﻿/*
Copyright 2009 Matías Fidemraizer (www.matiasfidemraizer.com).

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

using System;
using System.Linq;

#region "Custom namespace declarations"
using Comkarl;
#endregion

namespace Comkarl.Architecture.Patterns.MVC.Entities
{
    /// <summary>
    /// Represents the base class for a controller of notifications between models and/or views and supported actions by them.
    /// </summary>
    /// <typeparam name="TIdentifierName">
    /// The type for the identifier's name.
    /// </typeparam>
    public abstract class Controller<TIdentifierName> : Entity<TIdentifierName> where TIdentifierName : IEquatable<TIdentifierName>
    {
        #region "Fields"
        /// <summary>
        /// Retrieves the view manager's instance for the current controller
        /// </summary>
        private readonly ViewManager<TIdentifierName> viewManager;
        #endregion

        #region "Constructors"
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="id">An identifier for the current controller.</param>
        public Controller(ControllerIdentifier<TIdentifierName> id)
            : base(id)
        {
            viewManager = new ViewManager<TIdentifierName>();

            ViewManager.ViewAdded += new EventHandler(ViewManager_ViewAdded);
        }

        private void ViewManager_ViewAdded(object sender, EventArgs e)
        {
            View<TIdentifierName> currentView = ViewManager[ViewManager.Count - 1];

            currentView.AssociateToInstance(AssociatedInstance);
            currentView.AssociateToController(this);
        }
        #endregion

        #region "Events"
        /// <summary>
        /// Retrieves the observer's handlers for the situation on which an action is going to be done.
        /// </summary>
        private event ControllerActionEventHandler<TIdentifierName> beforeDoingAction;

        /// <summary>
        /// Retrieves the observer's handlers for the situation on wich an action has been done.
        /// </summary>
        private event ControllerActionEventHandler<TIdentifierName> afterDoingAction;

        /// <summary>
        /// Adds or removes observers for actions that are going to be done.
        /// </summary>
        public event ControllerActionEventHandler<TIdentifierName> BeforeDoingAction
        {
            add
            {
                beforeDoingAction += value;
            }
            remove
            {
                beforeDoingAction -= value;
            }
        }

        /// <summary>
        /// Adds or removes observers for actions that has been done.
        /// </summary>
        public event ControllerActionEventHandler<TIdentifierName> AfterDoingAction
        {
            add
            {
                afterDoingAction += value;
            }
            remove
            {
                afterDoingAction -= value;
            }
        }
        #endregion

        #region "Properties"
        /// <summary>
        /// Gets the controller's identifier.
        /// </summary>
        new public ControllerIdentifier<TIdentifierName> ID
        {
            get
            {
                return (ControllerIdentifier<TIdentifierName>)base.ID;
            }
        }

        /// <summary>
        /// Gets the instance of the view manager for the current controller.
        /// </summary>
        public ViewManager<TIdentifierName> ViewManager
        {
            get
            {
                return viewManager;
            }
        }
        #endregion

        #region "Abstract methods"
        /// <summary>
        /// In the inherited types, implements some kind of switch or redirection of an action to the model that may do some task.
        /// </summary>
        /// <typeparam name="TReturnValue">
        /// The underlying task return type of the resulting value after the execution of some action.
        /// </typeparam>
        /// <param name="incomingAction">
        /// An instance of an action that is going to be done.
        /// </param>
        /// <returns>
        /// A response to the action.
        /// </returns>
        protected abstract TReturnValue SelectAction<TReturnValue>(Comkarl.Architecture.Patterns.MVC.Entities.Action<TIdentifierName> incomingAction);
        #endregion

        #region "Public methods"
        /// <summary>
        /// Performs some provided action and redirects it to the model which executes some task.
        /// </summary>
        /// <typeparam name="TReturnValue">
        /// The underlying task return type of the resulting vaue after the execution of the incoming action.
        /// </typeparam>
        /// <param name="incomingAction">
        /// An instance of the incoming action that is going to be done.
        /// </param>
        /// <returns>
        /// A response to the action.
        /// </returns>
        public TReturnValue DoAction<TReturnValue>(Entities.Action<TIdentifierName> incomingAction)
        {
            try
            {
                // An action cannot be null.
                if (incomingAction == null)
                {
                    throw new ActionNullException<TIdentifierName>(AssociatedInstance, this, new ArgumentNullException("incomingAction"));
                }

                incomingAction.AssociateToInstance(AssociatedInstance);
                incomingAction.AssociateToController(this);

                // If there are observers, a notification is sent before performing the incoming action.
                if (beforeDoingAction != null)
                {
                    beforeDoingAction(this, new ControllerActionEventArgs<TIdentifierName>(incomingAction));
                }

                // Calling the SelectAction method in order to redirect the action to the model/s.
                return SelectAction<TReturnValue>(incomingAction);
            }
            catch (Exception e)
            {
                 throw e;
            }
            finally
            {
                // If there are observers, a notification is sent after performing the requested action.
                if (afterDoingAction != null)
                {
                    afterDoingAction(this, new ControllerActionEventArgs<TIdentifierName>(incomingAction));
                }
            }
        }

        /// <summary>
        /// Performs some provided action by specifying a delegate and redirects it to the model which executes some task.
        /// </summary>
        /// <param name="incomingActionDelegate">
        /// An instance of the incoming action delegate that is going to be done.
        /// </param>
        /// <returns>
        /// A response to the action.
        /// </returns>
        public TReturnValue DoAction<TReturnValue>(RuntimeActionDelegate incomingActionDelegate)
        {
            return (TReturnValue)DoAction<object>(new RuntimeAction<TIdentifierName, TReturnValue>(incomingActionDelegate));
        }
        #endregion
    }
}